Match plasma & serum samples

import pandas as pd
samples = pd.read_csv('data/sample_sheet.csv')
# must be Healthy Control Study and must pass MiSeq QC
samples = samples.loc[(samples['Study'] == 'Healthy Controls') & (samples['MISEQ.QC.PASS'] == 'PASS')]  
samples = samples.set_index('MT.Unique.ID').sort_values(by=['Participant.ID', 'Source'])
# capitalize & strip whitespace for consistency
for column in ['Gender', 'Race', 'Source']:
    samples[column] = samples[column].str.capitalize()
    samples[column] = samples[column].str.strip()
# use correct ontology terms
race_ontology = {'Asian': 'Asian',
 'Black or african american': 'African_American',
 'Mixed/asian & white': 'Multiracial',
 'Mixed/asian &black': 'Multiracial',
 'Mixed/black, white, asian': 'Multiracial',
 'Native hawiian or other pacific islander': 'Pacific_Islander',
 'Pacific islander': 'Pacific_Islander',
 'White': 'White'}
for id in samples.index:
    race = samples.at[id, 'Race']
    samples.at[id, 'Race'] = race_ontology[race] if race in race_ontology else 'Multiracial'
# get matched plasma & serum samples
mir_counts = pd.read_csv("data/get_canonical/canon_mir_counts.csv", index_col=0)
samples.index = pd.Index(['X' + str(row) for row in samples.index])
serum_part_ids = set(samples.loc[samples['Source'] =='Serum']['Participant.ID'])
matched_samples = samples.loc[samples['Participant.ID'].isin(serum_part_ids)]
matched_mir_counts = mir_counts[matched_samples.index]
matched_samples.to_csv('data/matched_plasma-serum_samples.csv')
matched_mir_counts.to_csv('data/matched_plasma-serum_mir_counts.csv')

Load the sample data and miRNA counts

samples <- read.csv('data/matched_plasma-serum_samples.csv')
samples <- subset(samples, Library.Generation.Set != "SetRecheck" )  # exclude SetRecheck samples
# sample X11 is outlier -- remove it and its match
outlier_id <- samples[samples$X=="X11",]$Participant.ID
samples <- samples[samples$Participant.ID != outlier_id,]
counts <- read.csv('data/matched_plasma-serum_mir_counts.csv')
samples$Participant.ID <- factor(samples$Participant.ID)  # ID is categorical, not numerical
samples$Sample.ID <- factor(samples$Sample.ID)
rownames(counts) = counts$X
rownames(samples) = samples$X
counts$X <- NULL  # remove extra column
counts <- counts[,rownames(samples)]
head(samples)
head(counts)

Filter

library(edgeR)
design <- model.matrix(~Participant.ID + Source, samples)
dge = DGEList(counts = counts, samples = samples)
# require miRNAs to have CPM > 1 in at least 2 samples
countsPerMillion <- edgeR::cpm(dge)
countCheck <- countsPerMillion > 1
head(countCheck)
                 X1  X107    X2  X108   X14  X110   X15   X18   X21  X113   X22  X114   X26  X115   X40  X116   X46  X119   X48  X120   X52
hsa-let-7a-3p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7a-5p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7b-3p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE
hsa-let-7b-5p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7c-3p FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
               X121   X56  X122   X59  X123   X60  X124  X62  X125   X64  X126   X66  X127   X67  X128   X68  X129   X69  X130  X131   X77
hsa-let-7a-3p  TRUE  TRUE FALSE  TRUE FALSE  TRUE FALSE TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE  TRUE
hsa-let-7a-5p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7b-3p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7b-5p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
hsa-let-7c-3p FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE
               X132  X133   X88  X134   X89  X135   X90  X136  X93  X137  X138
hsa-let-7a-3p FALSE FALSE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE TRUE  TRUE  TRUE
hsa-let-7a-5p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE
hsa-let-7b-3p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE
hsa-let-7b-5p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE
hsa-let-7c-3p FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE
hsa-let-7c-5p  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE TRUE  TRUE  TRUE
keep <- which(rowSums(countCheck) >= 2) 
dge <- dge[keep,]

Explore variance

library(SingleCellExperiment)
library(scater)
reads_sce <- SingleCellExperiment(assays=list(counts=dge$counts),  colData=dge$samples)
# remove unexpressed miRNAs
keep_feature <- rowSums(counts(reads_sce) > 0) > 0
reads_sce <- reads_sce[keep_feature, ]
reads_sce <- calculateQCMetrics(reads_sce)
Note that the names of some metrics have changed, see 'Renamed metrics' in ?calculateQCMetrics.
Old names are currently maintained for back-compatibility, but may be removed in future releases.
# create separate plasma & serum SCEs
plasma_samples <- subset(colData(reads_sce), Source == "Plasma")
plasma_counts <- as.data.frame(counts(reads_sce))[rownames(plasma_samples)]
plasma_sce <- SingleCellExperiment(assays=list(counts=as.matrix(plasma_counts)), colData=plasma_samples)
plasma_sce <- calculateQCMetrics(plasma_sce)
Note that the names of some metrics have changed, see 'Renamed metrics' in ?calculateQCMetrics.
Old names are currently maintained for back-compatibility, but may be removed in future releases.
serum_samples <- subset(colData(reads_sce), Source == "Serum")
serum_counts <- as.data.frame(counts(reads_sce))[rownames(serum_samples)]
serum_sce <- SingleCellExperiment(assays=list(counts=as.matrix(serum_counts)), colData=serum_samples)
serum_sce <- calculateQCMetrics(serum_sce)
Note that the names of some metrics have changed, see 'Renamed metrics' in ?calculateQCMetrics.
Old names are currently maintained for back-compatibility, but may be removed in future releases.
# log transform
cpm(reads_sce) <- calculateCPM(reads_sce)
reads_sce <- normalize(reads_sce)
using library sizes as size factors
logcounts(reads_sce) <- log2(calculateCPM(reads_sce) + 1)

Examine sources of variation

plotPCA(reads_sce, exprs_values = "logcounts", colour_by = "Library.Generation.Set", size_by = "total_features", shape_by = "Source")
non-plotting arguments like 'exprs_values' should go in 'run_args'

for (var in c("total_features", "Age", "Library.Generation.Set", "Index", "Participant.ID", "Source", "Race", "Gender", "Collection.Date")) {
  print(
    plotQC(reads_sce, type = "find-pcs", exprs_values = "logcounts", variable = var)
    )  + ggtitle(var) 
  }

Normalize & Remove unwanted sources of variation

library(RUVSeq)
library(ggplot2)
library(mvoutlier)
Loading required package: sgeostat
sROC 0.1-2 loaded
dge <- calcNormFactors(dge)
dge <- estimateGLMCommonDisp(dge, design)
dge <- estimateGLMTagwiseDisp(dge, design)
fit <- glmFit(dge, design)
res <- residuals(fit, type="deviance")
set <- newSeqExpressionSet(dge$counts, phenoData=dge$samples)
ruvr_sets <- list()
for(k in 1:5) {
  ruvr_sets[[k]] <- RUVr(set, row.names(dge), k=k, res)
  assay(reads_sce, paste("RUVr k=", toString(k))) <- log2(t(t(assayData(ruvr_sets[[k]])$normalizedCounts) / colSums(assayData(ruvr_sets[[k]])$normalizedCounts) * 1e6) + 1)
}
for(n in assayNames(reads_sce)) {
  print(
        plotPCA(
            reads_sce,
            colour_by = "Library.Generation.Set",
            size_by = "total_features",
            shape_by = "Source",
            exprs_values = n
        ) + ggtitle(n)
  )
}
non-plotting arguments like 'exprs_values' should go in 'run_args'

for(k in 1:5) {
  ruvr_sets[[k]]$set <- RUVr(set, row.names(dge), k=k, res)
  assay(reads_sce, paste("RUVr k=", toString(k))) <- log2(t(t(assayData(ruvr_sets[[k]]$set)$normalizedCounts) / colSums(assayData(ruvr_sets[[k]]$set)$normalizedCounts) * 1e6) + 1)
}
Error in `[[<-.data.frame`(`*tmp*`, i, value = new("SeqExpressionSet",  : 
  replacement has 989 rows, data has 53

Detect outliers

reads_sce <- runPCA(reads_sce, use_coldata = TRUE, detect_outliers = TRUE)
failed to find 'pct_counts_feature_control' in column metadatafailed to find 'total_features_by_counts_feature_control' in column metadatafailed to find 'log10_total_counts_endogenous' in column metadatafailed to find 'log10_total_counts_feature_control' in column metadata
outliers <- colnames(reads_sce)[reads_sce$outlier]
head(outliers)
character(0)

Examine sources of variance after removing unwanted variation

for (var in c("total_features", "Age", "Library.Generation.Set", "Index", "Participant.ID", "Source", "Race", "Gender", "Collection.Date")) {
  print(
    plotQC(reads_sce, type = "find-pcs", exprs_values = "RUVr1", variable = var)
    )  + ggtitle(var) 
}
Error in assay(object, exprs_values) : 
  'assay(<SingleCellExperiment>, i="character", ...)' invalid subscript 'i'
'RUVr1' not in names(assays(<SingleCellExperiment>))
for (var in c("total_features", "Age", "Library.Generation.Set", "Index", "Participant.ID", "Source", "Race", "Gender", "Collection.Date")) {
  print(
    plotQC(reads_sce, type = "find-pcs", exprs_values = "RUVr k= 2", variable = var)
    )  + ggtitle(var) 
}

Visualize top highly-expressed plasma-vs-serum miRNAs

library(RColorBrewer)
library(reshape2)
# Ryan's code, modified
norm.expr.matr <- exprs(reads_sce)
# Rank the mean expression values for plasma/serum miRs. Highest expression = 1
mean.expr.plasma.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Source=="Plasma"]))
mean.expr.serum.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Source=="Serum"]))
top_N <- 18  # top_N=18 resuls in 20 miRNAs in the plot
top.miRs <- row.names(norm.expr.matr)[mean.expr.plasma.rank <= top_N | mean.expr.serum.rank <= top_N] # get the names of the top miRs in plasma or serum
norm.expr.top <- norm.expr.matr[top.miRs, ] # Get the expression matrix for the top mIRs
norm.expr.melt <- reshape2::melt(norm.expr.top) # Convert your normalized expression matrix to a 3 column data.frame (row.name, col.name, expression value). Melt is in the dpylr package, I believe.
colnames(norm.expr.melt) <- c("miR.ID", "MT.Unique.ID", "norm.expr") # just so it's easier for me to tell you which columns I'm using.
norm.expr.melt$Source <- ""
for (row_num in 1:nrow(norm.expr.melt)){  # Pull the plasma/serum source value from the column metadata.
  mt_unique_id <- norm.expr.melt[row_num, ]$MT.Unique.ID
  norm.expr.melt[row_num, "Source"] <- as.character(samples[mt_unique_id,"Source"])
}
# This would be a simple boxplot with plasma/serum side-by-side. Overlaying the boxes over points takes a little more tweaking to get the dodge/width right, but it's doable.
ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN=median), y=norm.expr, fill=Source)) + geom_boxplot(pos="dodge", outlier.size=0.5) + 
  ggtitle("Top 20 Expressed miRNAs") + ylab("Normalized Expression") + xlab("miRNA ID") + 
  theme(panel.grid.major.x = element_line(size = 0.5, linetype = 'solid', colour = "grey"), 
        panel.grid.major.y = element_blank(),
        axis.text.x = element_text(angle = 90, hjust = 1))

ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN=median), y=norm.expr, fill=Source)) + geom_boxplot(pos="dodge", outlier.size=0.5) + 
  ggtitle("Top 20 Expressed miRNAs") + ylab("Normalized Expression") + xlab("miRNA ID") + 
  theme(panel.grid.major.y = element_line(size = 0.5, linetype = 'solid', colour = "grey"), 
        panel.grid.major.x = element_blank()) + coord_flip()

DE analysis with EdgeR

DE Analysis with DESeq2

library(DESeq2)  # TODO: use filtered & RUVSeq-corrected data
deseq2Data <- DESeqDataSetFromMatrix(countData = dge$counts, design = ~ Participant.ID + Source, colData = dge$samples)
deseq2Data <- DESeq(deseq2Data)
estimating size factors
estimating dispersions
gene-wise dispersion estimates
mean-dispersion relationship
final dispersion estimates
fitting model and testing
results <- results(deseq2Data, cooksCutoff=FALSE, independentFiltering=FALSE)
head(as.data.frame(results))

Create a heatmap of the variance-stabilizing-transformed expression data

library(pheatmap)   # TODO: only use top DE miRNAs
vstData <- varianceStabilizingTransformation(deseq2Data, blind=FALSE)  # transform the data
select <- order(rowMeans(counts(deseq2Data,normalized=TRUE)), decreasing=TRUE)
Source <- colData(deseq2Data)[,c("Source")]
annotations <- as.data.frame(Source)
rownames(annotations) <- colnames(deseq2Data)
pheatmap(assay(vstData)[select,], cluster_rows=TRUE, show_rownames=FALSE, cluster_cols=TRUE, annotation_col=annotations)

Create a heatmap of sample-to-sample distances

sampleDists <- dist(t(assay(vstData)))  # compute sample-to-sample distances
sampleDistMatrix <- as.matrix(sampleDists)
rownames(sampleDistMatrix) <- paste(vstData$Participant.ID, vstData$Source, sep=" - ")
colnames(sampleDistMatrix) <- NULL
colors <- colorRampPalette( rev(brewer.pal(9, "Blues")) )(255)
pheatmap(sampleDistMatrix,
         clustering_distance_rows=sampleDists,
         clustering_distance_cols=sampleDists,
         col=colors)

Create a PCA plot showing Age x Gender

plotPCA(
            reads_sce,
            colour_by = "Age",
            shape_by = "Gender",
            size_by = "total_features",
            exprs_values = "RUVr k= 2"
) + ggtitle("RUVSeq-Normalized Expression (k=2)") 
non-plotting arguments like 'exprs_values' should go in 'run_args'

LS0tCnRpdGxlOiAibWlSTkEgRGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgZm9yIG1hdGNoZWQgcGxhc21hICYgc2VydW0gc2FtcGxlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQojIE1hdGNoIHBsYXNtYSAmIHNlcnVtIHNhbXBsZXMKYGBge3B5dGhvbjN9CmltcG9ydCBwYW5kYXMgYXMgcGQKc2FtcGxlcyA9IHBkLnJlYWRfY3N2KCdkYXRhL3NhbXBsZV9zaGVldC5jc3YnKQojIG11c3QgYmUgSGVhbHRoeSBDb250cm9sIFN0dWR5IGFuZCBtdXN0IHBhc3MgTWlTZXEgUUMKc2FtcGxlcyA9IHNhbXBsZXMubG9jWyhzYW1wbGVzWydTdHVkeSddID09ICdIZWFsdGh5IENvbnRyb2xzJykgJiAoc2FtcGxlc1snTUlTRVEuUUMuUEFTUyddID09ICdQQVNTJyldICAKc2FtcGxlcyA9IHNhbXBsZXMuc2V0X2luZGV4KCdNVC5VbmlxdWUuSUQnKS5zb3J0X3ZhbHVlcyhieT1bJ1BhcnRpY2lwYW50LklEJywgJ1NvdXJjZSddKQojIGNhcGl0YWxpemUgJiBzdHJpcCB3aGl0ZXNwYWNlIGZvciBjb25zaXN0ZW5jeQpmb3IgY29sdW1uIGluIFsnR2VuZGVyJywgJ1JhY2UnLCAnU291cmNlJ106CiAgICBzYW1wbGVzW2NvbHVtbl0gPSBzYW1wbGVzW2NvbHVtbl0uc3RyLmNhcGl0YWxpemUoKQogICAgc2FtcGxlc1tjb2x1bW5dID0gc2FtcGxlc1tjb2x1bW5dLnN0ci5zdHJpcCgpCiMgdXNlIGNvcnJlY3Qgb250b2xvZ3kgdGVybXMKcmFjZV9vbnRvbG9neSA9IHsnQXNpYW4nOiAnQXNpYW4nLAogJ0JsYWNrIG9yIGFmcmljYW4gYW1lcmljYW4nOiAnQWZyaWNhbl9BbWVyaWNhbicsCiAnTWl4ZWQvYXNpYW4gJiB3aGl0ZSc6ICdNdWx0aXJhY2lhbCcsCiAnTWl4ZWQvYXNpYW4gJmJsYWNrJzogJ011bHRpcmFjaWFsJywKICdNaXhlZC9ibGFjaywgd2hpdGUsIGFzaWFuJzogJ011bHRpcmFjaWFsJywKICdOYXRpdmUgaGF3aWlhbiBvciBvdGhlciBwYWNpZmljIGlzbGFuZGVyJzogJ1BhY2lmaWNfSXNsYW5kZXInLAogJ1BhY2lmaWMgaXNsYW5kZXInOiAnUGFjaWZpY19Jc2xhbmRlcicsCiAnV2hpdGUnOiAnV2hpdGUnfQpmb3IgaWQgaW4gc2FtcGxlcy5pbmRleDoKICAgIHJhY2UgPSBzYW1wbGVzLmF0W2lkLCAnUmFjZSddCiAgICBzYW1wbGVzLmF0W2lkLCAnUmFjZSddID0gcmFjZV9vbnRvbG9neVtyYWNlXSBpZiByYWNlIGluIHJhY2Vfb250b2xvZ3kgZWxzZSAnTXVsdGlyYWNpYWwnCiMgZ2V0IG1hdGNoZWQgcGxhc21hICYgc2VydW0gc2FtcGxlcwptaXJfY291bnRzID0gcGQucmVhZF9jc3YoImRhdGEvZ2V0X2Nhbm9uaWNhbC9jYW5vbl9taXJfY291bnRzLmNzdiIsIGluZGV4X2NvbD0wKQpzYW1wbGVzLmluZGV4ID0gcGQuSW5kZXgoWydYJyArIHN0cihyb3cpIGZvciByb3cgaW4gc2FtcGxlcy5pbmRleF0pCnNlcnVtX3BhcnRfaWRzID0gc2V0KHNhbXBsZXMubG9jW3NhbXBsZXNbJ1NvdXJjZSddID09J1NlcnVtJ11bJ1BhcnRpY2lwYW50LklEJ10pCm1hdGNoZWRfc2FtcGxlcyA9IHNhbXBsZXMubG9jW3NhbXBsZXNbJ1BhcnRpY2lwYW50LklEJ10uaXNpbihzZXJ1bV9wYXJ0X2lkcyldCm1hdGNoZWRfbWlyX2NvdW50cyA9IG1pcl9jb3VudHNbbWF0Y2hlZF9zYW1wbGVzLmluZGV4XQptYXRjaGVkX3NhbXBsZXMudG9fY3N2KCdkYXRhL21hdGNoZWRfcGxhc21hLXNlcnVtX3NhbXBsZXMuY3N2JykKbWF0Y2hlZF9taXJfY291bnRzLnRvX2NzdignZGF0YS9tYXRjaGVkX3BsYXNtYS1zZXJ1bV9taXJfY291bnRzLmNzdicpCmBgYAojIyBMb2FkIHRoZSBzYW1wbGUgZGF0YSBhbmQgbWlSTkEgY291bnRzCmBgYHtyfQpzYW1wbGVzIDwtIHJlYWQuY3N2KCdkYXRhL21hdGNoZWRfcGxhc21hLXNlcnVtX3NhbXBsZXMuY3N2JykKc2FtcGxlcyA8LSBzdWJzZXQoc2FtcGxlcywgTGlicmFyeS5HZW5lcmF0aW9uLlNldCAhPSAiU2V0UmVjaGVjayIgKSAgIyBleGNsdWRlIFNldFJlY2hlY2sgc2FtcGxlcwojIHNhbXBsZSBYMTEgaXMgb3V0bGllciAtLSByZW1vdmUgaXQgYW5kIGl0cyBtYXRjaApvdXRsaWVyX2lkIDwtIHNhbXBsZXNbc2FtcGxlcyRYPT0iWDExIixdJFBhcnRpY2lwYW50LklECnNhbXBsZXMgPC0gc2FtcGxlc1tzYW1wbGVzJFBhcnRpY2lwYW50LklEICE9IG91dGxpZXJfaWQsXQpjb3VudHMgPC0gcmVhZC5jc3YoJ2RhdGEvbWF0Y2hlZF9wbGFzbWEtc2VydW1fbWlyX2NvdW50cy5jc3YnKQpzYW1wbGVzJFBhcnRpY2lwYW50LklEIDwtIGZhY3RvcihzYW1wbGVzJFBhcnRpY2lwYW50LklEKSAgIyBJRCBpcyBjYXRlZ29yaWNhbCwgbm90IG51bWVyaWNhbApzYW1wbGVzJFNhbXBsZS5JRCA8LSBmYWN0b3Ioc2FtcGxlcyRTYW1wbGUuSUQpCnJvd25hbWVzKGNvdW50cykgPSBjb3VudHMkWApyb3duYW1lcyhzYW1wbGVzKSA9IHNhbXBsZXMkWApjb3VudHMkWCA8LSBOVUxMICAjIHJlbW92ZSBleHRyYSBjb2x1bW4KY291bnRzIDwtIGNvdW50c1sscm93bmFtZXMoc2FtcGxlcyldCmhlYWQoc2FtcGxlcykKaGVhZChjb3VudHMpCmBgYAojIyBGaWx0ZXIKYGBge3J9CmxpYnJhcnkoZWRnZVIpCmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgoflBhcnRpY2lwYW50LklEICsgU291cmNlLCBzYW1wbGVzKQpkZ2UgPSBER0VMaXN0KGNvdW50cyA9IGNvdW50cywgc2FtcGxlcyA9IHNhbXBsZXMpCiMgcmVxdWlyZSBtaVJOQXMgdG8gaGF2ZSBDUE0gPiAxIGluIGF0IGxlYXN0IDIgc2FtcGxlcwpjb3VudHNQZXJNaWxsaW9uIDwtIGVkZ2VSOjpjcG0oZGdlKQpjb3VudENoZWNrIDwtIGNvdW50c1Blck1pbGxpb24gPiAxCmhlYWQoY291bnRDaGVjaykKa2VlcCA8LSB3aGljaChyb3dTdW1zKGNvdW50Q2hlY2spID49IDIpIApkZ2UgPC0gZGdlW2tlZXAsXQpgYGAKIyMgRXhwbG9yZSB2YXJpYW5jZQpgYGB7cn0KbGlicmFyeShTaW5nbGVDZWxsRXhwZXJpbWVudCkKbGlicmFyeShzY2F0ZXIpCnJlYWRzX3NjZSA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChhc3NheXM9bGlzdChjb3VudHM9ZGdlJGNvdW50cyksICBjb2xEYXRhPWRnZSRzYW1wbGVzKQojIHJlbW92ZSB1bmV4cHJlc3NlZCBtaVJOQXMKa2VlcF9mZWF0dXJlIDwtIHJvd1N1bXMoY291bnRzKHJlYWRzX3NjZSkgPiAwKSA+IDAKcmVhZHNfc2NlIDwtIHJlYWRzX3NjZVtrZWVwX2ZlYXR1cmUsIF0KcmVhZHNfc2NlIDwtIGNhbGN1bGF0ZVFDTWV0cmljcyhyZWFkc19zY2UpCiMgY3JlYXRlIHNlcGFyYXRlIHBsYXNtYSAmIHNlcnVtIFNDRXMKcGxhc21hX3NhbXBsZXMgPC0gc3Vic2V0KGNvbERhdGEocmVhZHNfc2NlKSwgU291cmNlID09ICJQbGFzbWEiKQpwbGFzbWFfY291bnRzIDwtIGFzLmRhdGEuZnJhbWUoY291bnRzKHJlYWRzX3NjZSkpW3Jvd25hbWVzKHBsYXNtYV9zYW1wbGVzKV0KcGxhc21hX3NjZSA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChhc3NheXM9bGlzdChjb3VudHM9YXMubWF0cml4KHBsYXNtYV9jb3VudHMpKSwgY29sRGF0YT1wbGFzbWFfc2FtcGxlcykKcGxhc21hX3NjZSA8LSBjYWxjdWxhdGVRQ01ldHJpY3MocGxhc21hX3NjZSkKc2VydW1fc2FtcGxlcyA8LSBzdWJzZXQoY29sRGF0YShyZWFkc19zY2UpLCBTb3VyY2UgPT0gIlNlcnVtIikKc2VydW1fY291bnRzIDwtIGFzLmRhdGEuZnJhbWUoY291bnRzKHJlYWRzX3NjZSkpW3Jvd25hbWVzKHNlcnVtX3NhbXBsZXMpXQpzZXJ1bV9zY2UgPC0gU2luZ2xlQ2VsbEV4cGVyaW1lbnQoYXNzYXlzPWxpc3QoY291bnRzPWFzLm1hdHJpeChzZXJ1bV9jb3VudHMpKSwgY29sRGF0YT1zZXJ1bV9zYW1wbGVzKQpzZXJ1bV9zY2UgPC0gY2FsY3VsYXRlUUNNZXRyaWNzKHNlcnVtX3NjZSkKIyBsb2cgdHJhbnNmb3JtCmNwbShyZWFkc19zY2UpIDwtIGNhbGN1bGF0ZUNQTShyZWFkc19zY2UpCnJlYWRzX3NjZSA8LSBub3JtYWxpemUocmVhZHNfc2NlKQpsb2djb3VudHMocmVhZHNfc2NlKSA8LSBsb2cyKGNhbGN1bGF0ZUNQTShyZWFkc19zY2UpICsgMSkKIyB2aXN1YWxpemUKaGlzdChyZWFkc19zY2UkdG90YWxfY291bnRzLCBicmVha3M9MTAwKSAgIyBjb3VudHMgcGVyIHNhbXBsZQpoaXN0KHJlYWRzX3NjZSR0b3RhbF9mZWF0dXJlcywgYnJlYWtzPTEwMCkgICMgY291bnRzIHBlciBtaVJOQQpwbG90UUMocmVhZHNfc2NlLCB0eXBlID0gImhpZ2hlc3QtZXhwcmVzc2lvbiIpICsgZ2d0aXRsZSgiUGxhc21hICYgU2VydW0iKQpwbG90UUMocGxhc21hX3NjZSwgdHlwZSA9ICJoaWdoZXN0LWV4cHJlc3Npb24iKSArIGdndGl0bGUoIlBsYXNtYSIpCnBsb3RRQyhzZXJ1bV9zY2UsIHR5cGUgPSAiaGlnaGVzdC1leHByZXNzaW9uIikgKyBnZ3RpdGxlKCJTZXJ1bSIpCnBsb3RRQyhyZWFkc19zY2UsIHR5cGU9ImV4cGxhbmF0b3J5LXZhcmlhYmxlcyIsIHZhcmlhYmxlcz1jKCJJbmRleCIsICJQYXJ0aWNpcGFudC5JRCIsICJDb2xsZWN0aW9uLkRhdGUiLCAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsICJNaVNlcS5RQy5SdW4iLCAiU291cmNlIiwgJ3RvdGFsX2ZlYXR1cmVzJywgIkFnZSIsICJSYWNlIiwgIlNleCIpKQpgYGAKIyMgRXhhbWluZSBzb3VyY2VzIG9mIHZhcmlhdGlvbgpgYGB7cn0KcGxvdFBDQShyZWFkc19zY2UsIGV4cHJzX3ZhbHVlcyA9ICJsb2djb3VudHMiLCBjb2xvdXJfYnkgPSAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLCBzaGFwZV9ieSA9ICJTb3VyY2UiKQpmb3IgKHZhciBpbiBjKCJ0b3RhbF9mZWF0dXJlcyIsICJBZ2UiLCAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsICJJbmRleCIsICJQYXJ0aWNpcGFudC5JRCIsICJTb3VyY2UiLCAiUmFjZSIsICJHZW5kZXIiLCAiQ29sbGVjdGlvbi5EYXRlIikpIHsKICBwcmludCgKICAgIHBsb3RRQyhyZWFkc19zY2UsIHR5cGUgPSAiZmluZC1wY3MiLCBleHByc192YWx1ZXMgPSAibG9nY291bnRzIiwgdmFyaWFibGUgPSB2YXIpCiAgICApICArIGdndGl0bGUodmFyKSAKICB9CmBgYAoKIyMgTm9ybWFsaXplICYgUmVtb3ZlIHVud2FudGVkIHNvdXJjZXMgb2YgdmFyaWF0aW9uCmBgYHtyfQpsaWJyYXJ5KFJVVlNlcSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KG12b3V0bGllcikKZGdlIDwtIGNhbGNOb3JtRmFjdG9ycyhkZ2UpCmRnZSA8LSBlc3RpbWF0ZUdMTUNvbW1vbkRpc3AoZGdlLCBkZXNpZ24pCmRnZSA8LSBlc3RpbWF0ZUdMTVRhZ3dpc2VEaXNwKGRnZSwgZGVzaWduKQpmaXQgPC0gZ2xtRml0KGRnZSwgZGVzaWduKQpyZXMgPC0gcmVzaWR1YWxzKGZpdCwgdHlwZT0iZGV2aWFuY2UiKQpzZXQgPC0gbmV3U2VxRXhwcmVzc2lvblNldChkZ2UkY291bnRzLCBwaGVub0RhdGE9ZGdlJHNhbXBsZXMpCnJ1dnJfc2V0cyA8LSBsaXN0KCkKZm9yKGsgaW4gMTo1KSB7CiAgcnV2cl9zZXRzW1trXV0gPC0gUlVWcihzZXQsIHJvdy5uYW1lcyhkZ2UpLCBrPWssIHJlcykKICBhc3NheShyZWFkc19zY2UsIHBhc3RlKCJSVVZyIGs9IiwgdG9TdHJpbmcoaykpKSA8LSBsb2cyKHQodChhc3NheURhdGEocnV2cl9zZXRzW1trXV0pJG5vcm1hbGl6ZWRDb3VudHMpIC8gY29sU3Vtcyhhc3NheURhdGEocnV2cl9zZXRzW1trXV0pJG5vcm1hbGl6ZWRDb3VudHMpICogMWU2KSArIDEpCn0KZm9yKG4gaW4gYXNzYXlOYW1lcyhyZWFkc19zY2UpKSB7CiAgcHJpbnQoCiAgICAgICAgcGxvdFBDQSgKICAgICAgICAgICAgcmVhZHNfc2NlLAogICAgICAgICAgICBjb2xvdXJfYnkgPSAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBzaGFwZV9ieSA9ICJTb3VyY2UiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSBuCiAgICAgICAgKSArIGdndGl0bGUobikKICApCn0KYGBgCiMjIERldGVjdCBvdXRsaWVycwpgYGB7cn0KcmVhZHNfc2NlIDwtIHJ1blBDQShyZWFkc19zY2UsIHVzZV9jb2xkYXRhID0gVFJVRSwgZGV0ZWN0X291dGxpZXJzID0gVFJVRSkKb3V0bGllcnMgPC0gY29sbmFtZXMocmVhZHNfc2NlKVtyZWFkc19zY2Ukb3V0bGllcl0KaGVhZChvdXRsaWVycykKYGBgCiMjIEV4YW1pbmUgc291cmNlcyBvZiB2YXJpYW5jZSBhZnRlciByZW1vdmluZyB1bndhbnRlZCB2YXJpYXRpb24KYGBge3J9CmZvciAodmFyIGluIGMoInRvdGFsX2ZlYXR1cmVzIiwgIkFnZSIsICJMaWJyYXJ5LkdlbmVyYXRpb24uU2V0IiwgIkluZGV4IiwgIlBhcnRpY2lwYW50LklEIiwgIlNvdXJjZSIsICJSYWNlIiwgIkdlbmRlciIsICJDb2xsZWN0aW9uLkRhdGUiKSkgewogIHByaW50KAogICAgcGxvdFFDKHJlYWRzX3NjZSwgdHlwZSA9ICJmaW5kLXBjcyIsIGV4cHJzX3ZhbHVlcyA9ICJSVVZyIGs9IDIiLCB2YXJpYWJsZSA9IHZhcikKICAgICkgICsgZ2d0aXRsZSh2YXIpIAp9CmBgYAojIyBWaXN1YWxpemUgdG9wIGhpZ2hseS1leHByZXNzZWQgcGxhc21hLXZzLXNlcnVtIG1pUk5BcwpgYGB7cn0KbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkocmVzaGFwZTIpCiMgUnlhbidzIGNvZGUsIG1vZGlmaWVkCm5vcm0uZXhwci5tYXRyIDwtIGV4cHJzKHJlYWRzX3NjZSkKIyBSYW5rIHRoZSBtZWFuIGV4cHJlc3Npb24gdmFsdWVzIGZvciBwbGFzbWEvc2VydW0gbWlScy4gSGlnaGVzdCBleHByZXNzaW9uID0gMQptZWFuLmV4cHIucGxhc21hLnJhbmsgPC0gcmFuaygtMSpyb3dNZWFucyhub3JtLmV4cHIubWF0clssIGNvbERhdGEocmVhZHNfc2NlKSRTb3VyY2U9PSJQbGFzbWEiXSkpCm1lYW4uZXhwci5zZXJ1bS5yYW5rIDwtIHJhbmsoLTEqcm93TWVhbnMobm9ybS5leHByLm1hdHJbLCBjb2xEYXRhKHJlYWRzX3NjZSkkU291cmNlPT0iU2VydW0iXSkpCnRvcF9OIDwtIDE4ICAjIHRvcF9OPTE4IHJlc3VscyBpbiAyMCBtaVJOQXMgaW4gdGhlIHBsb3QKdG9wLm1pUnMgPC0gcm93Lm5hbWVzKG5vcm0uZXhwci5tYXRyKVttZWFuLmV4cHIucGxhc21hLnJhbmsgPD0gdG9wX04gfCBtZWFuLmV4cHIuc2VydW0ucmFuayA8PSB0b3BfTl0gIyBnZXQgdGhlIG5hbWVzIG9mIHRoZSB0b3AgbWlScyBpbiBwbGFzbWEgb3Igc2VydW0Kbm9ybS5leHByLnRvcCA8LSBub3JtLmV4cHIubWF0clt0b3AubWlScywgXSAjIEdldCB0aGUgZXhwcmVzc2lvbiBtYXRyaXggZm9yIHRoZSB0b3AgbUlScwpub3JtLmV4cHIubWVsdCA8LSByZXNoYXBlMjo6bWVsdChub3JtLmV4cHIudG9wKSAjIENvbnZlcnQgeW91ciBub3JtYWxpemVkIGV4cHJlc3Npb24gbWF0cml4IHRvIGEgMyBjb2x1bW4gZGF0YS5mcmFtZSAocm93Lm5hbWUsIGNvbC5uYW1lLCBleHByZXNzaW9uIHZhbHVlKS4gTWVsdCBpcyBpbiB0aGUgZHB5bHIgcGFja2FnZSwgSSBiZWxpZXZlLgpjb2xuYW1lcyhub3JtLmV4cHIubWVsdCkgPC0gYygibWlSLklEIiwgIk1ULlVuaXF1ZS5JRCIsICJub3JtLmV4cHIiKSAjIGp1c3Qgc28gaXQncyBlYXNpZXIgZm9yIG1lIHRvIHRlbGwgeW91IHdoaWNoIGNvbHVtbnMgSSdtIHVzaW5nLgpub3JtLmV4cHIubWVsdCRTb3VyY2UgPC0gIiIKZm9yIChyb3dfbnVtIGluIDE6bnJvdyhub3JtLmV4cHIubWVsdCkpeyAgIyBQdWxsIHRoZSBwbGFzbWEvc2VydW0gc291cmNlIHZhbHVlIGZyb20gdGhlIGNvbHVtbiBtZXRhZGF0YS4KICBtdF91bmlxdWVfaWQgPC0gbm9ybS5leHByLm1lbHRbcm93X251bSwgXSRNVC5VbmlxdWUuSUQKICBub3JtLmV4cHIubWVsdFtyb3dfbnVtLCAiU291cmNlIl0gPC0gYXMuY2hhcmFjdGVyKHNhbXBsZXNbbXRfdW5pcXVlX2lkLCJTb3VyY2UiXSkKfQojIFRoaXMgd291bGQgYmUgYSBzaW1wbGUgYm94cGxvdCB3aXRoIHBsYXNtYS9zZXJ1bSBzaWRlLWJ5LXNpZGUuIE92ZXJsYXlpbmcgdGhlIGJveGVzIG92ZXIgcG9pbnRzIHRha2VzIGEgbGl0dGxlIG1vcmUgdHdlYWtpbmcgdG8gZ2V0IHRoZSBkb2RnZS93aWR0aCByaWdodCwgYnV0IGl0J3MgZG9hYmxlLgpnZ3Bsb3Qobm9ybS5leHByLm1lbHQsIGFlcyh4PXJlb3JkZXIobWlSLklELCBub3JtLmV4cHIsIEZVTj1tZWRpYW4pLCB5PW5vcm0uZXhwciwgZmlsbD1Tb3VyY2UpKSArIGdlb21fYm94cGxvdChwb3M9ImRvZGdlIiwgb3V0bGllci5zaXplPTAuNSkgKyAKICBnZ3RpdGxlKCJUb3AgMjAgRXhwcmVzc2VkIG1pUk5BcyIpICsgeWxhYigiTm9ybWFsaXplZCBFeHByZXNzaW9uIikgKyB4bGFiKCJtaVJOQSBJRCIpICsgCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKHNpemUgPSAwLjUsIGxpbmV0eXBlID0gJ3NvbGlkJywgY29sb3VyID0gImdyZXkiKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCmdncGxvdChub3JtLmV4cHIubWVsdCwgYWVzKHg9cmVvcmRlcihtaVIuSUQsIG5vcm0uZXhwciwgRlVOPW1lZGlhbiksIHk9bm9ybS5leHByLCBmaWxsPVNvdXJjZSkpICsgZ2VvbV9ib3hwbG90KHBvcz0iZG9kZ2UiLCBvdXRsaWVyLnNpemU9MC41KSArIAogIGdndGl0bGUoIlRvcCAyMCBFeHByZXNzZWQgbWlSTkFzIikgKyB5bGFiKCJOb3JtYWxpemVkIEV4cHJlc3Npb24iKSArIHhsYWIoIm1pUk5BIElEIikgKyAKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IDAuNSwgbGluZXR5cGUgPSAnc29saWQnLCBjb2xvdXIgPSAiZ3JleSIpLCAKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsgY29vcmRfZmxpcCgpCmBgYAoKIyBERSBhbmFseXNpcyB3aXRoIEVkZ2VSCmBgYHtyfQpsaWJyYXJ5KHBoZWF0bWFwKQojIGJhc2VkIG9uIFJ5YW4ncyBjb2RlLCBwYXNzIFJVVlNlcS1jb3JyZWN0ZWQgZGF0YSB0byBlZGdlUgpydXZyMiA8LSBydXZyX3NldHNbWzJdXQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH4gUGFydGljaXBhbnQuSUQgKyBTb3VyY2UgKyBXXzEgKyBXXzIsIHBEYXRhKHJ1dnIyKSkKZGdlIDwtIGVzdGltYXRlRGlzcChkZ2UsIGRlc2lnbiA9IGRlc2lnbiwgdGFnd2lzZSA9IFRSVUUsIHJvYnVzdCA9IFRSVUUpCmZpdCA8LSBnbG1GaXQoZGdlLCBkZXNpZ24pCiMgY29udHJhc3Qgc291cmNlIChwbGFzbWEgdnMgc2VydW0pCmxydCA8LSBnbG1MUlQoZml0LCBjb2VmPSJTb3VyY2VTZXJ1bSIpCnJlc3VsdHMgPC0gZGF0YS5mcmFtZSh0b3BUYWdzKGxydCwgbj1JbmYsIHNvcnQuYnk9IlBWYWx1ZSIsIHAudmFsdWU9MC4wNSkpCnNpZ19taVJzID0gbGlzdCgpCmZvciAobG9nRkNfdGhyZXNob2xkIGluIDE6MikgewogIHNpZ19taVJzW1tsb2dGQ190aHJlc2hvbGRdXSA8LSByb3duYW1lcyhyZXN1bHRzW3Jlc3VsdHMkUFZhbHVlIDwgMC4wNSAmIGFicyhyZXN1bHRzJGxvZ0ZDKSA+IGxvZ0ZDX3RocmVzaG9sZCxdKSAgIyBmaWx0ZXIgYnkgcC12YWx1ZSBhbmQgbG9nRkMKfQpzaWdfbWlSc1tbM11dIDwtIHJvd25hbWVzKHJlc3VsdHNbcmVzdWx0cyRQVmFsdWUgPCAwLjA1LF1bMDo1MCxdKQojIGhlYXRtYXBzIG9mIHNpZ25pZmljYW50IERFIG1pUk5BcwpTb3VyY2UgPC0gcERhdGEocnV2cjIpWyxjKCJTb3VyY2UiKV0KYW5ub3RhdGlvbnMgPC0gYXMuZGF0YS5mcmFtZShTb3VyY2UpCnJvd25hbWVzKGFubm90YXRpb25zKSA8LSByb3duYW1lcyhwRGF0YShydXZyMikpCmZvcihtaVJfbGlzdCBpbiBzaWdfbWlScykgewogIHBoZWF0bWFwKG5vcm0uZXhwci5tYXRyW21pUl9saXN0LF0sIGNsdXN0ZXJfcm93cz1UUlVFLCBzaG93X3Jvd25hbWVzPVRSVUUsIGNsdXN0ZXJfY29scz1UUlVFLCBhbm5vdGF0aW9uX2NvbD1hbm5vdGF0aW9ucywgbWFpbj0iRGlmZmVyZW50aWFsbHkgRXhwcmVzc2VkIG1pUk5BcyIpCn0KYGBgCiMgREUgQW5hbHlzaXMgd2l0aCBERVNlcTIKYGBge3J9CmxpYnJhcnkoREVTZXEyKSAgIyBUT0RPOiB1c2UgZmlsdGVyZWQgJiBSVVZTZXEtY29ycmVjdGVkIGRhdGEKZGVzZXEyRGF0YSA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KGNvdW50RGF0YSA9IGRnZSRjb3VudHMsIGRlc2lnbiA9IH4gUGFydGljaXBhbnQuSUQgKyBTb3VyY2UsIGNvbERhdGEgPSBkZ2Ukc2FtcGxlcykKZGVzZXEyRGF0YSA8LSBERVNlcShkZXNlcTJEYXRhKQpyZXN1bHRzIDwtIHJlc3VsdHMoZGVzZXEyRGF0YSwgY29va3NDdXRvZmY9RkFMU0UsIGluZGVwZW5kZW50RmlsdGVyaW5nPUZBTFNFKQpoZWFkKGFzLmRhdGEuZnJhbWUocmVzdWx0cykpCmBgYAoKIyMgQ3JlYXRlIGEgaGVhdG1hcCBvZiB0aGUgdmFyaWFuY2Utc3RhYmlsaXppbmctdHJhbnNmb3JtZWQgZXhwcmVzc2lvbiBkYXRhCmBgYHtyfQogICAjIFRPRE86IG9ubHkgdXNlIHRvcCBERSBtaVJOQXMKdnN0RGF0YSA8LSB2YXJpYW5jZVN0YWJpbGl6aW5nVHJhbnNmb3JtYXRpb24oZGVzZXEyRGF0YSwgYmxpbmQ9RkFMU0UpICAjIHRyYW5zZm9ybSB0aGUgZGF0YQpzZWxlY3QgPC0gb3JkZXIocm93TWVhbnMoY291bnRzKGRlc2VxMkRhdGEsbm9ybWFsaXplZD1UUlVFKSksIGRlY3JlYXNpbmc9VFJVRSkKU291cmNlIDwtIGNvbERhdGEoZGVzZXEyRGF0YSlbLGMoIlNvdXJjZSIpXQphbm5vdGF0aW9ucyA8LSBhcy5kYXRhLmZyYW1lKFNvdXJjZSkKcm93bmFtZXMoYW5ub3RhdGlvbnMpIDwtIGNvbG5hbWVzKGRlc2VxMkRhdGEpCnBoZWF0bWFwKGFzc2F5KHZzdERhdGEpW3NlbGVjdCxdLCBjbHVzdGVyX3Jvd3M9VFJVRSwgc2hvd19yb3duYW1lcz1GQUxTRSwgY2x1c3Rlcl9jb2xzPVRSVUUsIGFubm90YXRpb25fY29sPWFubm90YXRpb25zKQpgYGAKCiMjIENyZWF0ZSBhIGhlYXRtYXAgb2Ygc2FtcGxlLXRvLXNhbXBsZSBkaXN0YW5jZXMKYGBge3J9CnNhbXBsZURpc3RzIDwtIGRpc3QodChhc3NheSh2c3REYXRhKSkpICAjIGNvbXB1dGUgc2FtcGxlLXRvLXNhbXBsZSBkaXN0YW5jZXMKc2FtcGxlRGlzdE1hdHJpeCA8LSBhcy5tYXRyaXgoc2FtcGxlRGlzdHMpCnJvd25hbWVzKHNhbXBsZURpc3RNYXRyaXgpIDwtIHBhc3RlKHZzdERhdGEkUGFydGljaXBhbnQuSUQsIHZzdERhdGEkU291cmNlLCBzZXA9IiAtICIpCmNvbG5hbWVzKHNhbXBsZURpc3RNYXRyaXgpIDwtIE5VTEwKY29sb3JzIDwtIGNvbG9yUmFtcFBhbGV0dGUoIHJldihicmV3ZXIucGFsKDksICJCbHVlcyIpKSApKDI1NSkKcGhlYXRtYXAoc2FtcGxlRGlzdE1hdHJpeCwKICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzPXNhbXBsZURpc3RzLAogICAgICAgICBjbHVzdGVyaW5nX2Rpc3RhbmNlX2NvbHM9c2FtcGxlRGlzdHMsCiAgICAgICAgIGNvbD1jb2xvcnMpCmBgYAoKIyMgQ3JlYXRlIGEgUENBIHBsb3Qgc2hvd2luZyBBZ2UgeCBHZW5kZXIKYGBge3J9CnBsb3RQQ0EoCiAgICAgICAgICAgIHJlYWRzX3NjZSwKICAgICAgICAgICAgY29sb3VyX2J5ID0gIkFnZSIsCiAgICAgICAgICAgIHNoYXBlX2J5ID0gIkdlbmRlciIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSAiUlVWciBrPSAyIgopICsgZ2d0aXRsZSgiUlVWU2VxLU5vcm1hbGl6ZWQgRXhwcmVzc2lvbiAoaz0yKSIpIApgYGAKYGBge3J9CnNhdmUuaW1hZ2UoImRpZmZfZXhwcl9wbGFzbWFfdnNfc2VydW0uUkRhdGEiKQpgYGAKCg==